/* MIT License
 *
 * Copyright (c) 2012 Reuben Smith, Miami University
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.droidlike.vsig;


import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * TrafficSignal.
 * 
 * @author smithre5
 */
class TrafficSignal extends TrafficModule
{
    /**
     * Creates a TrafficSignal without any associated script.
     * 
     * @param executor the ExecutorService that will run this module
     * @param assetManager the AssetManager used to create the model
     * @return a TrafficSignal if successful; null otherwise
     */
    public static TrafficSignal create(ExecutorService executor, AssetManager assetManager)
    {
        if (executor == null || assetManager == null) {
            return null;
        }
        
        TrafficSignal signal = new TrafficSignal(executor);
        signal.setRate(1.0f);
        signal.setScript(VSIG.getProfile().getLibraryFiles()[0].getPath());
        signal.createGeometry(assetManager);
        signal.setSpatial(signal.boxNode);
        signal.moduleInput = new long[3];
        
        return signal;
    }
    
    
    /**
     * Attaches the TrafficSignal to the given scene at the given position.
     * 
     * @param root the scene or parent node
     * @param position the offset from the root
     */
    public void attachAt(Node root, Vector3f position)
    {
        if (root == null) {
            return;
        }
        
        root.attachChild(spatial);
        
        if (position != null) {
            spatial.setLocalTranslation(position);
        }
    }
    
    
    public boolean isGreen()
    {
        if (moduleOutput == null || (moduleOutput[0] & 1) != 1) {
            return false;
        }
        
        return true;
    }
    
    
    public boolean isRed()
    {
        if (moduleOutput == null || (moduleOutput[0] & 4) != 4) {
            return false;
        }
        
        return true;
    }
    
    
    public boolean isYellow()
    {
        if (moduleOutput == null || (moduleOutput[0] & 2) != 2) {
            return false;
        }
        
        return true;
    }
    
    
    @Override
    public void update(float tpf)
    {
        super.update(tpf);
        
        if (!isModuleReady() || Util.isBlank(getScript())) {
            return;
        }
        
        // TODO Improve clock?
        //      Link reset to something.
        //      Link input to external things and not just feedback.
        
        moduleInput[0] = (moduleInput[0] == 0 ? 1 : 0);
        moduleInput[1] = 0;
        moduleInput[2] = (moduleOutput == null ? 1 : moduleOutput[0]);
        
        moduleOutput = executor.callModule(moduleID, moduleInput);
        if (moduleOutput == null) {
            Logger.getLogger(TrafficSignal.class.getName()).log(Level.WARNING, 
                "Executor returned null from script '" + getScript() + "'.", (Object)null);
            spatial.removeControl(this);
            return;
        }
        
        ColorRGBA nextColor = null;
        for (int i = 0; i < 3; ++i) {
            if ((moduleOutput[0] & 1 << i) == 1 << i) {
                nextColor = LIT_COLORS[i];
            }
            else {
                nextColor = UNLIT_COLORS[i];
            }
            
            lightMaterials[i].setColor("Color", nextColor);
        }
        
        super.updated();
    }
    
    
    /**
     * Default constructor.
     * 
     * @param executor associated ExecutorService
     */
    private TrafficSignal(ExecutorService executor)
    {
        super(executor);
        ++instancedCount;
    }
    
    
    /**
     * Builds the model and materials.
     * 
     * @param assetManager the AssetManager to be used
     */
    private void createGeometry(AssetManager assetManager)
    {
        boxMaterial = new Material(assetManager,
            "Common/MatDefs/Misc/Unshaded.j3md");
        boxMaterial.setColor("Color", BOX_COLOR);
        
        boxNode = new Node("signal:" + instancedCount);
        Geometry boxGeom = new Geometry(boxNode.getName() + "-geom");
        Box boxMesh = new Box(0.25f, 5f, 0.25f);
        boxGeom.setMaterial(boxMaterial);
        boxGeom.setMesh(boxMesh);
        boxNode.attachChild(boxGeom);
        
        lightNodes = new Node[LIT_COLORS.length];
        lightMaterials = new Material[LIT_COLORS.length];
        for (int i = lightMaterials.length; --i >= 0;) {
            lightMaterials[i] = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
            lightMaterials[i].setColor("Color", UNLIT_COLORS[i]);
            
            lightNodes[i] = new Node(boxNode.getName() + "-light:" + i);
            Geometry lightGeom = new Geometry(lightNodes[i].getName() + "-geom");
            Cylinder lightMesh = new Cylinder(4, 8, 0.75f, 0.25f, true);
            lightGeom.setMaterial(lightMaterials[i]);
            lightGeom.setMesh(lightMesh);
            lightNodes[i].attachChild(lightGeom);
            //lightGeom.rotate(0, FastMath.HALF_PI, 0);
            lightGeom.move(0, 2 * i, 0.4f);
            
            boxNode.attachChild(lightNodes[i]);
        }
        
        boxNode.rotate(0, -FastMath.HALF_PI, 0);
    }
    
    
    private static final ColorRGBA BOX_COLOR = ColorRGBA.LightGray;
    private static final ColorRGBA[] LIT_COLORS = new ColorRGBA[]{
        new ColorRGBA(0, 1, 0, 1),
        new ColorRGBA(1, 1, 0, 1),
        new ColorRGBA(1, 0, 0, 1),
    };
    private static final ColorRGBA[] UNLIT_COLORS = new ColorRGBA[]{
        new ColorRGBA(0, 0.3f, 0, 1),
        new ColorRGBA(0.3f, 0.3f, 0, 1),
        new ColorRGBA(0.3f, 0, 0, 1),
    };
    private static long instancedCount = 0;
    
    private Node        boxNode;
    private Material    boxMaterial;
    private Node[]      lightNodes;
    private Material[]  lightMaterials;
}